#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include <cassert>

using namespace std;

// 先描述再组织：1.start函数实现直接创建多个线程，  2.主线程（接收/创建）任务放入线程池
// 3.没有任务，线程则阻塞，用一个互斥变量和条件变量控制访问任务队列   4.pop任务之后直接由线程在池中自行完成

template <class T>
class PthreadPool
{
public:
    PthreadPool(size_t num = 4)
        : is_start(false), _threadnum(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ~PthreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    static void *pool_routine(void *args) // 线程函数类内部，调用会有this指针，pthread_create无法默认传this指针，只能显示传，当然也可写在类外
    {
        PthreadPool<T> *pb = static_cast<PthreadPool<T> *>(args);
        pthread_detach(pthread_self()); // 线程分离，主线程自己完成自己的任务
        while (true)
        {
            // 循环处理接收任务
            pb->lock();
            while (pb->is_empty())
            {
                // 任务队列为空就阻塞
                pb->wait_for_task();
            }
            // 拿出任务并执行
            T task = pb->pop();//pop里面不能锁哦，在外面调用的时候锁就行，不然就自己阻塞自己了
            pb->unlock();

            // for debug
            int one, two;
            char oper;
            task.get(&one, &two, &oper);
            // 规定，所有的任务都必须有一个run方法
            cout <<"时间："<< time(0) << " 新线程完成计算任务: " << one << oper << two << "=" << task.run() << "\n";
        }
    }
    void start()
    {
        assert(!is_start);

        for (int i = 0; i < _threadnum; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, pool_routine, this); // 线程执行进程池代码，把自己传过去,该线程任务是在线程池内部直接完成的
        }
        is_start = true;
    }
    void push(const T &task)
    {
        // 控制其中一个线程向任务队列放任务,互斥访问，放完就可以唤醒某一个线程了
        lock();
        _taskqueue.push(task);
        signal_for_task();
        unlock();
    }

private:
    void lock() { pthread_mutex_lock(&_mutex); }
    void unlock() { pthread_mutex_unlock(&_mutex); }
    void wait_for_task() { pthread_cond_wait(&_cond, &_mutex); }
    void signal_for_task() { pthread_cond_signal(&_cond); }
    bool is_empty() { return _taskqueue.empty(); }
    T pop()
    {
        // lock(); // 可以锁上，可以不锁。这里用的条件变量阻塞线程
        T temp = _taskqueue.front();
        _taskqueue.pop();
        // unlock();
        return temp;
    }

private:
    size_t _threadnum;
    queue<T> _taskqueue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    bool is_start; // 判断是否已启动，防止重复启动
};